/*
 * Copyright (C) 2008,2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

#define r0			thread_self->r0

/*
 * Prototypes
 */
static eint32_t
offsets(efunction_t *function);

static ecode_t
switches(ehash_t *hash, eint32_t offset);

/*
 * Initialization
 */
static char *exceptions[] = {
    "nothing",
    "out of memory",
    "floating point error",
    "out of bounds",
    "null dereference",
    "type mismatch",
    "input output error",
    "invalid argument",
    "not a number",
    "not an integer",
    "not a real number",
    "not a rational number",
    "not a 32 bits integer",
    "not a finite real number",
};

/*
 * Implementation
 */
eobject_t
evm(ethread_t *thr)
{
    register eint32_t	 *ip;
    register void *const *label;
    register evalue_t	 *p0;
    evalue_t		  r1;
    eint32_t		  type;
    eint32_t		  offset;
    eint32_t		  length;

#define predict()							\
    label = (void *const *)((eword_t )&&noop_label + labels[*(euint32_t *)ip++])
#define next()			goto *label
#define L(label)		(eword_t)label - (eword_t)&&noop_label
#define V(name)			&&name##_label
    static const eword_t labels[] = {
	L(V(noop)),			L(V(nil)),
	L(V(int)),			L(V(long)),
	L(V(float)),			L(V(ll)),
	L(V(ld)),			L(V(ldt)),
	L(V(lb)),			L(V(lbt)),
	L(V(ls)),			L(V(lh)),
	L(V(lhref)),			L(V(lv)),
	L(V(lvi)),			L(V(lvref)),
	L(V(lr)),			L(V(lrref)),
	L(V(lc)),			L(V(lref)),
	L(V(pd)),			L(V(pdt)),
	L(V(pb)),			L(V(pbt)),
	L(V(pv)),			L(V(pvi)),
	L(V(pr)),			L(V(pp)),
	L(V(anon)),			L(V(sd)),
	L(V(sdt)),			L(V(sb)),
	L(V(sbt)),			L(V(ss)),
	L(V(sst)),			L(V(ssv)),
	L(V(sh)),			L(V(shref)),
	L(V(sv)),			L(V(svi)),
	L(V(svref)),			L(V(sr)),
	L(V(srref)),			L(V(sc)),
	L(V(sref)),			L(V(jt)),
	L(V(jf)),			L(V(j)),
	L(V(jh)),			L(V(jv)),
	L(V(enter)),			L(V(begin)),
	L(V(off)),			L(V(push)),
	L(V(pusht)),			L(V(pushv)),
	L(V(pop)),			L(V(call)),
	L(V(meth)),			L(V(ecm)),
	L(V(blt)),			L(V(ret)),
	L(V(rett)),			L(V(reti)),
	L(V(retit)),			L(V(ne)),
	L(V(lt)),			L(V(le)),
	L(V(eq)),			L(V(ge)),
	L(V(gt)),			L(V(and)),
	L(V(or)),			L(V(xor)),
	L(V(mul2)),			L(V(div2)),
	L(V(shl)),			L(V(shr)),
	L(V(add)),			L(V(sub)),
	L(V(mul)),			L(V(div)),
	L(V(trunc2)),			L(V(rem)),
	L(V(atan2)),			L(V(pow)),
	L(V(hypot)),			L(V(complex)),
	L(V(inc)),			L(V(dec)),
	L(V(bool)),			L(V(not)),
	L(V(com)),			L(V(inv)),
	L(V(neg)),			L(V(signbit)),
	L(V(signum)),			L(V(rational)),
	L(V(integer_p)),		L(V(rational_p)),
	L(V(float_p)),			L(V(real_p)),
	L(V(complex_p)),		L(V(number_p)),
	L(V(finite_p)),			L(V(inf_p)),
	L(V(nan_p)), 			L(V(num)),
	L(V(den)),			L(V(real)),
	L(V(imag)),			L(V(arg)),
	L(V(conj)),			L(V(floor)),
	L(V(trunc)), 			L(V(round)),
	L(V(ceil)),			L(V(abs)),
	L(V(sqrt)),			L(V(cbrt)),
	L(V(sin)),			L(V(cos)),
	L(V(tan)),			L(V(asin)),
	L(V(acos)),			L(V(atan)),
	L(V(sinh)),			L(V(cosh)),
	L(V(tanh)),			L(V(asinh)),
	L(V(acosh)),			L(V(atanh)),
	L(V(proj)),			L(V(exp)),
	L(V(log)),			L(V(log2)),
	L(V(log10)),			L(V(new)),
	L(V(mv)),			L(V(vnew)),
	L(V(vnewi)),			L(V(vmv)),
	L(V(renew)),			L(V(renewi)),
	L(V(fref)),			L(V(apply)),
	L(V(aret)),			L(V(szof)),
	L(V(szofva)),			L(V(szofdim)),
	L(V(szofdimi)),			L(V(tpof)),
	L(V(subtpof)),			L(V(rankof)),
	L(V(vas)),			L(V(val)),
	L(V(lva)),			L(V(lvai)),
	L(V(sva)),			L(V(svai)),
	L(V(pva)),			L(V(try)),
	L(V(catch)),			L(V(throw)),
	L(V(unwind)),			L(V(exit)),
	L(V(setup)),			L(V(intp_0)),
	L(V(intp_v)),			L(V(intp_n1)),
	L(V(realp_v)),			L(V(nump_0)),
	L(V(nump_v)),
    };
#undef V
#undef L

    if (thr != tmain) {
	thread_self = thr;
	empcache_setup();
	for (p0 = thr->esp - 3; p0 >= thr->ess; p0--) {
	    switch (p0->t) {
		case t_mpz:	enew_mpz(&p0->v.o, p0->v.o);	break;
		case t_mpq:	enew_mpq(&p0->v.o, p0->v.o);	break;
		case t_mpr:	enew_mpr(&p0->v.o, p0->v.o);	break;
		case t_cqq:	enew_cqq(&p0->v.o, p0->v.o);	break;
		case t_mpc:	enew_mpc(&p0->v.o, p0->v.o);	break;
		default:					break;
	    }
	}
    }

    /* in case gc is running and will try to inspect this thread stack */
    ethreads_lock();
    thr->running = 1;
    thr->except = except_nothing;

    ip = thr->eip;
    r0.t = r1.t = t_void;
    r0.v.o = r1.v.o = null;

    /* arm signal exception handler and unlock threads linked list */
    if (sigsetjmp(thr->env, 1) == 0)
	ethreads_unlock();
    /* coming from signal handler or siglongjmp from builtin code */
    else if (thr->except == except_segmentation_violation)
	eabort((eint8_t *)(ip - 1), "segmentation violation");
    else if (thr->tryoff < 0) {
	if (r0.t != t_int || r0.v.i <= 0 ||
	    r0.v.i >= except_unhandled_exception)
	    eabort((eint8_t *)(ip - 1), "unknown");
	else
	    eabort((eint8_t *)(ip - 1), exceptions[r0.v.i]);
    }
    else {
	ip = thr->ess[thr->tryoff + 3].v.o;
	r0.t = t_int;
	r0.v.i = thr->except;
	thr->except = except_nothing;
	/* internal vm exceptions are (currently) of type int */
	goto noop_label;
    }

    /* start executing bytecode */
    goto noop_label;

    /* multi codes */
#define L(name)			name##_label
L(noop):
    predict();
    next();
L(nil):
    predict();
    r0.t = t_void;
    r0.v.o = null;
    next();
L(int):
    r0.t = t_int;
    r0.v.i = *ip++;
    predict();
    next();
L(long):
    r0.t = t_int;
    r0.v.i = *(eint_t *)ip;
    ip += 2;
    predict();
    next();
L(float):
    r0.t = t_float;
    r0.v.d = *(efloat_t *)ip;
    ip += 2;
    predict();
    next();
L(ll):
    offset = *ip++;
    predict();
    evm_ll(offset);
    next();
L(ld):
    offset = *ip++;
    predict();
    evm_ld(offset);
    next();
L(ldt):
    offset = *ip++;
    type = *ip++;
    predict();
    evm_ldt(offset, type);
    next();
L(lb):
    offset = *ip++;
    predict();
    evm_lb(offset);
    next();
L(lbt):
    offset = *ip++;
    type = *ip++;
    predict();
    evm_lbt(offset, type);
    next();
L(ls):
    offset = *ip++;
    predict();
    evm_ls(offset);
    next();
L(lh):
    predict();
    evm_lh();
    next();
L(lhref):
    offset = *ip++;
    predict();
    evm_lhref(offset);
    next();
L(lv):
    length = *ip++;
    predict();
    evm_lv(length);
    next();
L(lvi):
    offset = *ip++;
    predict();
    evm_lvi(offset);
    next();
L(lvref):
    offset = *ip++;
    length = *ip++;
    predict();
    evm_lvref(offset, length);
    next();
L(lr):
    length = *ip++;
    offset = *ip++;
    type = *ip++;
    predict();
    evm_lr(length, offset, type);
    next();
L(lrref):
    {
	eint32_t	arg;
	length = *ip++;
	offset = *ip++;
	type = *ip++;
	arg = *ip++;
	predict();
	evm_lrref(length, offset, type, arg);
    }
    next();
L(lc):
    offset = *ip++;
    type = *ip++;
    predict();
    evm_lc(offset, type);
    next();
L(lref):
    predict();
    evm_lref();
    next();
L(pd):
    r0.t = t_globalref;
    r0.v.o = eds + *ip++;
    predict();
    next();
L(pdt):
    offset = *ip++;
    predict();
    evm_pdt(offset);
    next();
L(pb):
    r0.t = t_stackref;
    r0.v.o = thr->ebp + *ip++;
    predict();
    next();
L(pbt):
    r0.t = t_t_stackref;
    r0.v.o = thr->ebp + *ip++;
    predict();
    next();
L(pv):
    offset = *ip++;
    predict();
    evm_pv(offset);
    next();
L(pvi):
    offset = *ip++;
    predict();
    evm_pvi(offset);
    next();
L(pr):
    length = *ip++;
    offset = *ip++;
    type = *ip++;
    predict();
    evm_pr(length, offset, type);
    next();
L(pp):
    predict();
    evm_pp();
    next();
L(anon):
    type = *ip++;
    length = *ip++;
    predict();
    evm_anon(type, length);
    next();
L(sd):
    offset = *ip++;
    predict();
    evm_sd(offset);
    next();
L(sdt):
    offset = *ip++;
    type = *ip++;
    predict();
    evm_sdt(offset, type);
    next();
L(sb):
    offset = *ip++;
    predict();
    evm_sb(offset);
    next();
L(sbt):
    offset = *ip++;
    type = *ip++;
    predict();
    evm_sbt(offset, type);
    next();
L(ss):
    offset = *ip++;
    predict();
    evm_ss(offset);
    next();
L(sst):
    offset = *ip++;
    type = *ip++;
    predict();
    evm_sst(offset, type);
    next();
L(ssv):
    offset = *ip++;
    predict();
    evm_ssv(offset);
    next();
L(sh):
    predict();
    evm_sh();
    next();
L(shref):
    offset = *ip++;
    predict();
    evm_shref(offset);
    next();
L(sv):
    offset = *ip++;
    predict();
    evm_sv(offset);
    next();
L(svi):
    offset = *ip++;
    predict();
    evm_svi(offset);
    next();
L(svref):
    offset = *ip++;
    predict();
    evm_svref(offset);
    next();
L(sr):
    length = *ip++;
    offset = *ip++;
    type = *ip++;
    predict();
    evm_sr(length, offset, type);
    next();
L(srref):
    offset = *ip++;
    predict();
    evm_srref(offset);
    next();
L(sc):
    offset = *ip++;
    type = *ip++;
    predict();
    evm_sc(offset, type);
    next();
L(sref):
    predict();
    evm_sref();
    next();
L(jt):
    if (evm_jt())
	ip += *ip;
    else
	++ip;
    predict();
    next();
L(jf):
    if (evm_jf())
	ip += *ip;
    else
	++ip;
    predict();
    next();
L(j):
    ip += *ip;
    predict();
    next();
L(jh):
    offset = *ip;
    ip += (eint32_t)(eword_t)evm_jh(offset);
    predict();
    next();
L(jv):
    offset = *ip;
    ip += (eint32_t)(eword_t)evm_jv(offset);
    predict();
    next();
L(enter):
    {
	eint32_t	 l = *ip++;
	eint32_t	 e = *ip++;
	eint32_t	 ac = *ip++;
	eint32_t	 lc = *ip++;
	eint32_t	*t = ip;
	ip += ac + lc;
	predict();
	evm_enter(l, e, ac, lc, t);
    }
    next();
L(begin):
    offset = *ip++;
    predict();
    evm_begin(offset);
    next();
L(off):
    offset = *ip++;
    predict();
    evm_off(offset);
    next();
L(push):
    predict();
    evm_push();
    next();
L(pusht):
    offset = *ip++;
    predict();
    evm_pusht(offset);
    next();
L(pushv):
    predict();
    evm_pushv();
    next();
L(pop):
    predict();
    evm_pop();
    next();
L(call):
    offset = *ip++;
    ip = evm_call(offset, ip);
    predict();
    next();
L(meth):
    offset = *ip++;
    type = *ip++;
    ip = evm_meth(offset, type, ip);
    predict();
    next();
L(ecm):
    offset = *ip++;
    type = *ip++;
    ip = evm_ecm(offset, type, ip);
    predict();
    next();
L(blt):
    offset = *ip++;
    length = *ip++;
    predict();
    evm_blt(offset, length);
    next();
L(ret):
    ip = evm_ret(1);
    predict();
    next();
L(rett):
    offset = *ip;
    ip = evm_rett(1, offset);
    predict();
    next();
L(reti):
    offset = *ip;
    ip = evm_ret(offset);
    predict();
    next();
L(retit):
    offset = *ip++;
    type = *ip;
    ip = evm_rett(offset, type);
    predict();
    next();
L(ne):
    predict();
    evm_ne();
    next();
L(lt):
    predict();
    evm_lt();
    next();
L(le):
    predict();
    evm_le();
    next();
L(eq):
    predict();
    evm_eq();
    next();
L(ge):
    predict();
    evm_ge();
    next();
L(gt):
    predict();
    evm_gt();
    next();
L(and):
    predict();
    evm_and();
    next();
L(or):
    predict();
    evm_or();
    next();
L(xor):
    predict();
    evm_xor();
    next();
L(mul2):
    predict();
    evm_mul2();
    next();
L(div2):
    predict();
    evm_div2();
    next();
L(shl):
    predict();
    evm_shl();
    next();
L(shr):
    predict();
    evm_shr();
    next();
L(add):
    predict();
    evm_add();
    next();
L(sub):
    predict();
    evm_sub();
    next();
L(mul):
    predict();
    evm_mul();
    next();
L(div):
    predict();
    evm_div();
    next();
L(trunc2):
    predict();
    evm_trunc2();
    next();
L(rem):
    predict();
    evm_rem();
    next();
L(atan2):
    predict();
    evm_atan2();
    next();
L(pow):
    predict();
    evm_pow();
    next();
L(hypot):
    predict();
    evm_hypot();
    next();
L(complex):
    predict();
    evm_complex();
    next();
L(inc):
    predict();
    evm_inc();
    next();
L(dec):
    predict();
    evm_dec();
    next();
L(bool):
    predict();
    evm_bool();
    next();
L(not):
    predict();
    evm_not();
    next();
L(com):
    predict();
    evm_com();
    next();
L(inv):
    predict();
    evm_inv();
    next();
L(neg):
    predict();
    evm_neg();
    next();
L(signbit):
    predict();
    evm_signbit();
    next();
L(signum):
    predict();
    evm_signum();
    next();
L(rational):
    predict();
    evm_rational();
    next();
L(integer_p):
    predict();
    evm_integer_p();
    next();
L(rational_p):
    predict();
    evm_rational_p();
    next();
L(float_p):
    predict();
    evm_float_p();
    next();
L(real_p):
    predict();
    evm_real_p();
    next();
L(complex_p):
    predict();
    evm_complex_p();
    next();
L(number_p):
    predict();
    evm_number_p();
    next();
L(finite_p):
    predict();
    evm_finite_p();
    next();
L(inf_p):
    predict();
    evm_inf_p();
    next();
L(nan_p):
    predict();
    evm_nan_p();
    next();
L(num):
    predict();
    evm_num();
    next();
L(den):
    predict();
    evm_den();
    next();
L(real):
    predict();
    evm_real();
    next();
L(imag):
    predict();
    evm_imag();
    next();
L(arg):
    predict();
    evm_arg();
    next();
L(conj):
    predict();
    evm_conj();
    next();
L(floor):
    predict();
    evm_floor();
    next();
L(trunc):
    predict();
    evm_trunc();
    next();
L(round):
    predict();
    evm_round();
    next();
L(ceil):
    predict();
    evm_ceil();
    next();
L(abs):
    predict();
    evm_abs();
    next();
L(sqrt):
    predict();
    evm_sqrt();
    next();
L(cbrt):
    predict();
    evm_cbrt();
    next();
L(sin):
    predict();
    evm_sin();
    next();
L(cos):
    predict();
    evm_cos();
    next();
L(tan):
    predict();
    evm_tan();
    next();
L(asin):
    predict();
    evm_asin();
    next();
L(acos):
    predict();
    evm_acos();
    next();
L(atan):
    predict();
    evm_atan();
    next();
L(sinh):
    predict();
    evm_sinh();
    next();
L(cosh):
    predict();
    evm_cosh();
    next();
L(tanh):
    predict();
    evm_tanh();
    next();
L(asinh):
    predict();
    evm_asinh();
    next();
L(acosh):
    predict();
    evm_acosh();
    next();
L(atanh):
    predict();
    evm_atanh();
    next();
L(proj):
    predict();
    evm_proj();
    next();
L(exp):
    predict();
    evm_exp();
    next();
L(log):
    predict();
    evm_log();
    next();
L(log2):
    predict();
    evm_log2();
    next();
L(log10):
    predict();
    evm_log10();
    next();
L(new):
    type = *ip++;
    length = *ip++;
    predict();
    evm_new(type, length);
    next();
L(mv):
    offset = *ip++;
    predict();
    evm_mv(offset);
    next();
L(vnew):
    type = *ip++;
    length = *ip++;
    predict();
    evm_vnew(type, length);
    next();
L(vnewi):
    {
	eint32_t	*dims;
	type = *ip++;
	length = *ip++;
	offset = *ip++;
	dims = ip;
	ip += offset;
	predict();
	evm_vnewi(type, length, offset, dims);
    }
    next();
L(vmv):
    offset = *ip++;
    predict();
    evm_vmv(offset);
    next();
L(renew):
    predict();
    evm_renew();
    next();
L(renewi):
    offset = *ip++;
    predict();
    evm_renewi(offset);
    next();
L(fref):
    offset = *ip++;
    length = *ip++;
    type = *ip++;
    predict();
    evm_fref(offset, length, type);
    next();
L(apply):
    offset = *ip++;
    ip = evm_apply(offset, ip);
    predict();
    next();
L(aret):
    predict();
    evm_aret();
    next();
L(szof):
    predict();
    evm_szof();
    next();
L(szofva):
    predict();
    evm_szofva();
    next();
L(szofdim):
    predict();
    evm_szofdim();
    next();
L(szofdimi):
    offset = *ip++;
    predict();
    evm_szofdimi(offset);
    next();
L(tpof):
    predict();
    evm_tpof();
    next();
L(subtpof):
    predict();
    evm_subtpof();
    next();
L(rankof):
    predict();
    evm_rankof();
    next();
L(vas):
    offset = *ip++;
    predict();
    evm_vas(offset);
    next();
L(val):
    offset = *ip++;
    predict();
    evm_val(offset);
    next();
L(lva):
    offset = *ip++;
    predict();
    evm_lva(offset);
    next();
L(lvai):
    length = *ip++;
    offset = *ip++;
    predict();
    evm_lvai(length, offset);
    next();
L(sva):
    offset = *ip++;
    predict();
    evm_sva(offset);
    next();
L(svai):
    length = *ip++;
    offset = *ip++;
    predict();
    evm_svai(length, offset);
    next();
L(pva):
    offset = *ip++;
    predict();
    evm_pva(offset);
    next();
L(try):
    evm_try(ip);
    /* proceed to execute non exception code */
    ip += 2;
    predict();
    next();
L(catch):
    predict();
    evm_catch();
    next();
L(throw):
    offset = *ip;
    ip = evm_throw(offset);
    predict();
    next();
L(unwind):
    predict();
    evm_unwind();
    next();
L(exit):
    evm_exit();
    return (null);
L(setup):
    offset = *ip++;
    type = *ip++;
    predict();
    evm_setup(offset, type);
    next();
L(intp_0):
    predict();
    evm_intp_0();
    next();
L(intp_v):
    predict();
    evm_intp_v();
    next();
L(intp_n1):
    predict();
    evm_intp_n1();
    next();
L(realp_v):
    predict();
    evm_realp_v();
    next();
L(nump_0):
    predict();
    evm_nump_0();
    next();
L(nump_v):
    predict();
    evm_nump_v();
    next();
#undef L
}

void
init_emit(void)
{
}

void
finish_emit(void)
{
    eint32_t	 index;
    eint32_t	 offset;
    efunction_t	*lambda;
    erecord_t	*record;
    esymbol_t	*symbol;

    ecs[0] = fn_root->code;
    for (offset = 1; offset < ecs_idx; offset++) {
	symbol = ecs[offset];
	lambda = symbol->value;
	if (!efunction_p(lambda) || !lambda->defined)
	    eerror("function '%p' not defined", symbol->name);
	ecs[offset] = lambda->code;
	lambda->code = null;
    }

    /* first pass - check for errors */
    for (offset = 0; offset < ets_idx; offset++) {
	record = record_vector[offset];
	assert(offset == (record->type & ~t_pointer));
	if (record->methods) {
	    for (index = 0; index < record->methods->count; index++) {
		symbol = ets[offset].methods[index].code;
		lambda = symbol->value;
		if (!efunction_p(lambda) || !lambda->defined)
		    eerror("method '%p.%p' not defined",
			   record->name->name, emethod_name(record, symbol));
		ets[offset].methods[index].code = lambda->code;
	    }
	}
    }

    /* second pass - release methods memory */
    for (offset = 0; offset < ets_idx; offset++) {
	record = record_vector[offset];
	if (record->methods) {
	    for (index = 0; index < record->methods->count; index++) {
		symbol = record->vmethod[index];
		lambda = symbol->value;
		lambda->code = null;
	    }
	}
    }
}

void
eemit(efunction_t *function)
{
    eint32_t		 i;
    eint32_t		*base;
    east_node_t		*node;
    eint32_t		*data;
    east_list_t		*list;
    evector_t		*name;
    enote_t		*note;
    eint32_t		 code;
    eint32_t		 length;

    length = offsets(function);
    if (cfg_verbose) {
	ewrite_function(function);
	if (cfg_verbose > 3)
	    ewrite_code(function->code);
    }
    v_check(1);
    enew_object((eobject_t *)&data, t_int32, length);
    base = data;
    v_push(base);

    name = null;
    enew_note((eobject_t *)&note, (eint8_t *)base,
	      function->name ? function->name->name : null);
    /* length already known */
    eset_note(note, length);

    for (list = function->code; list; list = east_next(list)) {
	node = east_node(list);
	code = node->code;
	switch (code) {
		/* labels are not store in the code */
	    case eop_label:			case eop_block:
		break;
	    case eop_nil:			case eop_lref:
	    case eop_lh:			case eop_sh:
	    case eop_pp:			case eop_sref:
	    case eop_ret:			case eop_push:
	    case eop_pushv:			case eop_pop:
	    case eop_ne:			case eop_lt:
	    case eop_le:			case eop_eq:
	    case eop_ge:			case eop_gt:
	    case eop_and:			case eop_or:
	    case eop_xor:			case eop_mul2:
	    case eop_div2:			case eop_shl:
	    case eop_shr:			case eop_add:
	    case eop_sub:			case eop_mul:
	    case eop_div:			case eop_trunc2:
	    case eop_rem:			case eop_atan2:
	    case eop_pow:			case eop_hypot:
	    case eop_complex:			case eop_inc:
	    case eop_dec:			case eop_bool:
	    case eop_not:			case eop_com:
	    case eop_inv:			case eop_neg:
	    case eop_signbit:			case eop_signum:
	    case eop_rational:			case eop_integer_p:
	    case eop_rational_p:		case eop_float_p:
	    case eop_real_p:			case eop_complex_p:
	    case eop_number_p:			case eop_finite_p:
	    case eop_inf_p:			case eop_nan_p:
	    case eop_num:			case eop_den:
	    case eop_real:			case eop_imag:
	    case eop_arg:			case eop_conj:
	    case eop_floor:			case eop_trunc:
	    case eop_round:			case eop_ceil:
	    case eop_abs:			case eop_sqrt:
	    case eop_cbrt:			case eop_sin:
	    case eop_cos:			case eop_tan:
	    case eop_asin:			case eop_acos:
	    case eop_atan:			case eop_sinh:
	    case eop_cosh:			case eop_tanh:
	    case eop_asinh:			case eop_acosh:
	    case eop_atanh:			case eop_proj:
	    case eop_exp:			case eop_log:
	    case eop_log2:			case eop_log10:
	    case eop_renew:			case eop_aret:
	    case eop_szof:			case eop_szofva:
	    case eop_szofdim:			case eop_tpof:
	    case eop_subtpof:			case eop_rankof:
	    case eop_try:			case eop_catch:
	    case eop_unwind:			case eop_exit:
	    case eop_intp_0:			case eop_intp_v:
	    case eop_intp_n1:			case eop_realp_v:
	    case eop_nump_0:			case eop_nump_v:
		*data++ = code;
		break;

		/* int+int codes */
	    case eop_int:
		*data++ = code;
		*data++ = immediate_lo(node);
		break;
	    case eop_ll:
		*data++ = code;
		*data++ = immediate_offset(node);
		break;
	    case eop_begin:			case eop_lva:
	    case eop_pva:			case eop_sva:
	    case eop_val:			case eop_vas:
		*data++ = code;
		*data++ = node->cstack.length;
		break;
	    case eop_lhref:			case eop_shref:
		*data++ = code;
		*data++ = node->chash.arg;
		break;
	    case eop_lv:			case eop_sv:
	    case eop_pv:			case eop_off:
		*data++ = code;
		*data++ = node->cvector.rank;
		break;
	    case eop_lvi:			case eop_svi:
	    case eop_pvi:
		*data++ = code;
		*data++ = node->cvector.offset;
		break;
	    case eop_svref:			case eop_szofdimi:
		*data++ = code;
		*data++ = node->cvector.arg;
		break;
	    case eop_srref:
		*data++ = code;
		*data++ = node->crecord.arg;
		break;
	    case eop_mv:
		*data++ = code;
		*data++ = node->crecord.record->size;
		break;
	    case eop_vmv:			case eop_renewi:
		*data++ = code;
		*data++ = node->cvector.length;
		break;
	    case eop_ls:			case eop_ss:
	    case eop_ssv:
		*data++ = code;
		*data++ = node->cstack.offset;
		break;
	    case eop_pusht:
		*data++ = code;
		*data++ = node->cstack.type;
		break;

	    case eop_apply:
		*data++ = code;
		*data++ = node->capply.length;
		break;

	    case eop_rett:
		*data++ = code;
		*data++ = node->creturn.type;
		break;
	    case eop_reti:
		*data++ = code;
		*data++ = node->creturn.length;
		break;

	    case eop_throw:
		*data++ = code;
		*data++ = node->cexcept.arg;
		break;

	    case eop_ld:			case eop_lb:
	    case eop_sd:			case eop_sb:
	    case eop_pd:			case eop_pdt:
	    case eop_pb:			case eop_pbt:
		*data++ = code;
		*data++ = node->csymbol.symbol->offset;
		break;

	    case eop_call:
		*data++ = code;
		*data++ = node->capply.offset;
		break;
	    case eop_j:				case eop_jt:
	    case eop_jf:
		*data++ = code;
		*data++ = node->clabel.offset;
		break;
	    case eop_jh:
		code = switches(els[immediate_offset(node)],
				immediate_offset(node));
		*data++ = code;
		*data++ = immediate_offset(node);
		break;

		/* int+int+int codes */
	    case eop_ldt:			case eop_lbt:
	    case eop_sdt:			case eop_sbt:
		*data++ = code;
		*data++ = node->csymbol.symbol->offset;
		*data++ = node->csymbol.symbol->type;
		break;
	    case eop_sst:
		*data++ = code;
		*data++ = node->cstack.offset;
		*data++ = node->cstack.type;
		break;
	    case eop_lvref:
		*data++ = code;
		*data++ = node->cvector.rank;
		*data++ = node->cvector.arg;
		break;
	    case eop_lc:			case eop_sc:
		*data++ = code;
		*data++ = node->csymbol.symbol->offset;
		*data++ = node->csymbol.symbol->type;
		break;
	    case eop_retit:
		*data++ = code;
		*data++ = node->creturn.length;
		*data++ = node->creturn.type;
		break;
	    case eop_new:
		*data++ = code;
		*data++ = node->crecord.record->type;
		*data++ = node->crecord.record->size;
		break;
	    case eop_anon:
		*data++ = code;
		*data++ = node->crecord.record->type;
		if (node->crecord.record->type)
		    *data++ = node->crecord.record->size;
		else
		    *data++ = sizeof(eobject_t);
		break;
	    case eop_lvai:			case eop_svai:
		*data++ = code;
		*data++ = node->cstack.length;
		*data++ = node->cstack.offset;
		break;
	    case eop_setup:
		*data++ = code;
		*data++ = node->csymbol.symbol->offset;
		*data++ = node->csymbol.symbol->type;
		break;
	    case eop_vnew:
		*data++ = code;
		*data++ = node->cvector.type;
		*data++ = node->cvector.rank;
		break;
	    case eop_blt:
		*data++ = code;
		*data++ = node->capply.offset;
		*data++ = node->capply.length;
		break;
	    case eop_meth:			case eop_ecm:
		*data++ = code;
		*data++ = node->capply.offset;
		*data++ = node->capply.type;
		break;

		/* int+int+int+int codes */
	    case eop_fref:
		*data++ = code;
		*data++ = node->capply.offset;
		*data++ = node->capply.length;
		*data++ = node->capply.arg;
		break;
	    case eop_lr:			case eop_sr:
	    case eop_pr:
		*data++ = code;
		*data++ = node->crecord.record->type;
		*data++ = node->crecord.symbol->offset;
		*data++ = node->crecord.symbol->type;
		break;

		/* int+int+int+int codes */
	    case eop_lrref:
		*data++ = code;
		*data++ = node->crecord.record->type;
		*data++ = node->crecord.symbol->offset;
		*data++ = node->crecord.symbol->type;
		*data++ = node->crecord.arg;
		break;

		/* int+long codes */
	    case eop_long:
		*data++ = code;
		*(eint_t *)data = immediate_int(node);
		data += 2;
		break;
	    case eop_float:
		*data++ = code;
		*(efloat_t *)data = immediate_float(node);
		data += 2;
		break;

		/* int+int+int+int[] codes */
	    case eop_vnewi:
		*data++ = code;
		*data++ = node->cvector.type;
		*data++ = node->cvector.length;
		*data++ = node->cvector.rank;
		for (i = 0; i < node->cvector.rank; i++)
		    *data++ = node->cvector.dims[i];
		break;

		/* int+int+int+int+int[] codes */
	    case eop_enter:
		*data++ = code;
		*data++ = node->center.local;
		*data++ = node->center.extra;
		*data++ = node->center.acount;
		*data++ = node->center.lcount;
		for (i = 0; i < node->center.acount + node->center.lcount; i++)
		    *data++ = node->center.types[i];
		break;

	    case eop_note:
		eannotate(note, node->cnote.name, node->cnote.line,
			  (data - base) << 2);
		break;

	    default:
		abort();
	}
    }

    efinish_note(note);

    if (cfg_verbose > 1)
	edisassemble(base, length >> 2);

    function->code = (east_list_t *)base;
    v_dec();
}

static eint32_t
offsets(efunction_t *function)
{
    eint32_t		 i;
    eint32_t		 jump;
    east_node_t		*node;
    east_list_t		*list;
    ehash_t		*hash;
    ebool_t		 again;
    eentry_t		*jhent;
    elabel_t		*label;
    elabel_t		*entry;
    eint32_t		 offset;
    eint32_t		 target;
    eint32_t		 distance;

    label = null;
    do {
	jump = offset = 0;
	again = false;
	for (list = function->code; list; list = east_next(list)) {
	    node = east_node(list);
	    switch (node->code) {
		case eop_label:
		    assert(label == null);
		    label = node->clabel.label;
		case eop_note:			case eop_block:
		    continue;

		    /* int codes */
		case eop_nil:			case eop_lref:
		case eop_lh:			case eop_sh:
		case eop_pp:			case eop_sref:
		case eop_push:			case eop_pushv:
		case eop_pop:			case eop_ret:
		case eop_ne:			case eop_lt:
		case eop_le:			case eop_eq:
		case eop_ge:			case eop_gt:
		case eop_and:			case eop_or:
		case eop_xor:			case eop_mul2:
		case eop_div2:			case eop_shl:
		case eop_shr:			case eop_add:
		case eop_sub:			case eop_mul:
		case eop_div:			case eop_trunc2:
		case eop_rem:			case eop_atan2:
		case eop_pow:			case eop_hypot:
		case eop_complex:		case eop_inc:
		case eop_dec:			case eop_bool:
		case eop_not:			case eop_com:
		case eop_inv:			case eop_neg:
		case eop_signbit:		case eop_signum:
		case eop_rational:		case eop_integer_p:
		case eop_rational_p:		case eop_float_p:
		case eop_real_p:		case eop_complex_p:
		case eop_number_p:		case eop_finite_p:
		case eop_inf_p:			case eop_nan_p:
		case eop_num:			case eop_den:
		case eop_real:			case eop_imag:
		case eop_arg:			case eop_conj:
		case eop_floor:			case eop_trunc:
		case eop_round:			case eop_ceil:
		case eop_abs:			case eop_sqrt:
		case eop_cbrt:			case eop_sin:
		case eop_cos:			case eop_tan:
		case eop_asin:			case eop_acos:
		case eop_atan:			case eop_sinh:
		case eop_cosh:			case eop_tanh:
		case eop_asinh:			case eop_acosh:
		case eop_atanh:			case eop_proj:
		case eop_exp:			case eop_log:
		case eop_log2:			case eop_log10:
		case eop_renew:			case eop_aret:
		case eop_szof:			case eop_szofva:
		case eop_szofdim:		case eop_tpof:
		case eop_subtpof:		case eop_rankof:
		case eop_try:			case eop_unwind:
		case eop_catch:			case eop_exit:
		case eop_intp_0:		case eop_intp_v:
		case eop_intp_n1:		case eop_realp_v:
		case eop_nump_0:		case eop_nump_v:
		    jump = offset;
		    ++offset;
		    break;

		    /* int+int codes */
		case eop_jt:		case eop_jf:
		case eop_j:
		    /* cannot be null  */
		    entry = node->clabel.label;
		    if (entry->value == null)
			again = true;
		    else {
			target = eget_fixnum(entry->value);
			distance = target - (offset + 1);
			node->clabel.offset = distance;
		    }
		    jump = offset;
		    offset += 2;
		    break;

		case eop_jh:
		    jump = offset;
		    hash = els[immediate_offset(node)];
		    for (i = 0; i < hash->size; i++) {
			for (jhent = hash->entries[i];
			     jhent;
			     jhent = jhent->next) {
			    node = jhent->value;
			    entry = node->clabel.label;
			    if (entry->value == null)
				again = true;
			    else {
				target = eget_fixnum(entry->value);
				distance = target - (offset + 1);
				node->clabel.offset = distance;
			    }
			}
		    }
		    offset += 2;
		    break;

		case eop_int:			case eop_ld:
		case eop_lb:			case eop_ll:
		case eop_ls:			case eop_lhref:
		case eop_lv:			case eop_lvi:
		case eop_sd:			case eop_sb:
		case eop_pd:			case eop_pdt:
		case eop_pb:			case eop_pbt:
		case eop_pv:			case eop_pvi:
		case eop_ss:			case eop_ssv:
		case eop_pusht:			case eop_shref:
		case eop_sv:			case eop_svi:
		case eop_svref:			case eop_srref:
		case eop_begin:			case eop_off:
		case eop_call:			case eop_rett:
		case eop_reti:			case eop_mv:
		case eop_vmv:			case eop_renewi:
		case eop_apply:			case eop_vas:
		case eop_val:			case eop_lva:
		case eop_sva:			case eop_pva:
		case eop_szofdimi:		case eop_throw:
		    jump = offset;
		    offset += 2;
		    break;

		    /* int+int+int codes */
		case eop_ldt:			case eop_lbt:
		case eop_lvref:			case eop_sdt:
		case eop_sbt:			case eop_sst:
		case eop_retit:			case eop_setup:
		case eop_anon:			case eop_lc:
		case eop_sc:			case eop_meth:
		case eop_ecm:			case eop_blt:
		case eop_lvai:			case eop_svai:
		case eop_new:			case eop_vnew:
		    jump = offset;
		    offset += 3;
		    break;

		    /* int+int+int+int codes */
		case eop_lr:			case eop_sr:
		case eop_pr:			case eop_fref:
		    jump = offset;
		    offset += 4;
		    break;

		    /* int+int+int+int+int codes */
		case eop_lrref:
		    jump = offset;
		    offset += 5;
		    break;

		    /* int+long codes */
		case eop_long:			case eop_float:
		    jump = offset;
		    offset += 3;
		    break;

		    /* int+int+int+int[] codes */
		case eop_vnewi:
		    jump = offset;
		    offset += 4 + node->cvector.rank;
		    break;

		    /* int+int+int+int+int[] codes */
		case eop_enter:
		    jump = offset;
		    offset += 5 + node->center.acount + node->center.lcount;
		    break;

		default:
		    abort();
	    }

	    if (label) {
		label->value = efixnum(jump);
		label = null;
	    }
	}
    } while (again);

    return (offset << 2);
}

static ecode_t
switches(ehash_t *hash, eint32_t offset)
{
    eint32_t		 i;
    ecode_t		 code;
    east_node_t		*node;
    eint32_t		 size;
    eentry_t		*entry;
    eint32_t		*array;
    ecase_t		*cases;
    eint32_t		 index;
    eint32_t		 length;
    eint32_t		 clashes;
    eint_t		 integer;
    evswitch_t		*vswitch;
    ehswitch_t		*hswitch;
    eint32_t		 fallback;
    eint_t		 imin, imax;

    /* default address */
    entry = eget_hash(hash, null);
    assert(entry != null);

    /* fallback is used to fill vswitch unused entries, so that it is
     * not required to check for an invalid 0 distance jump at runtime */
    node = entry->value;
    fallback = node->clabel.offset;
    /* remove the 'default' entry as it is no longer used */
    erem_hash(hash, entry);

    /* hash->count is guaranteed to not be zero */
    size = hash->count;
    /* if size is not already a power of two */
    if (size & (size - 1)) {
	for (i = 1 << 30; i >= 0; i >>= 1)
	    if (size & i)
		break;
	size = i << 1;
	/* should only assert on integer wrap */
	assert(size >= hash->count);
    }

    v_check(1);
    enew_object((eobject_t *)&array, t_void, (size / 32 + 1) * sizeof(eint32_t));
    v_push(array);

    /* set all bits and simplify code below */
    size -= 1;

    /* check min/max values, and if there are any clashes */
    clashes = 0;
    imax = 0x8000000000000000L;
    imin = 0x7fffffffffffffffL;
    for (i = 0; i < hash->size; i++) {
	for (entry = hash->entries[i]; entry; entry = entry->next) {
	    integer = eget_integer(entry->name);
	    if (integer > imax)
		imax = integer;
	    if (integer < imin)
		imin = integer;
	    integer &= size;
	    if (array[integer / 32] & (1 << (integer & 31)))
		++clashes;
	    else
		array[integer / 32] |= 1 << (integer & 31);
	}
    }

    if (clashes == 0 && imax - imin <= size) {
	/* faster case */
	enew_object((eobject_t *)&vswitch, t_void, sizeof(evswitch_t) +
		    sizeof(ecase_t) * size);
	v_top() = vswitch;
	vswitch->imin = imin;
	vswitch->imax = imax;
	vswitch->mask = size;
	/* dont need to check for a zero distance jump at runtime */
	for (i = 0; i <= size; i++)
	    vswitch->cases[i] = fallback;
	for (i = 0; i < hash->size; i++) {
	    for (entry = hash->entries[i]; entry; entry = entry->next) {
		integer = eget_integer(entry->name);
		index = integer & size;
		node = entry->value;
		vswitch->cases[index] = node->clabel.offset;
	    }
	}
	code = eop_jv;
	els[offset] = vswitch;
    }
    else {
	/* slower case */
	enew_object((eobject_t *)&hswitch, t_switch, sizeof(ehswitch_t) +
		    sizeof(eobject_t) * size);
	v_top() = hswitch;
	hswitch->imin = imin;
	hswitch->imax = imax;
	hswitch->mask = size;
	for (i = 0; i < hash->size; i++) {
	    for (entry = hash->entries[i]; entry; entry = entry->next) {
		integer = eget_integer(entry->name);
		index = integer & size;
		if ((cases = hswitch->cases[index]) == null) {
		    enew_object((eobject_t *)&cases, t_resize | t_void,
				sizeof(ecase_t));
		    hswitch->cases[index] = cases;
		    length = 0;
		}
		else {
		    /* a clash */
		    length = eobject_length(cases) + sizeof(ecase_t);
		    eresize_object((eobject_t *)(hswitch->cases + index),
				   length);
		    cases = hswitch->cases[index];
		    length = length / sizeof(ecase_t) - 1;
		}
		cases[length].value = integer;
		node = entry->value;
		cases[length].label = node->clabel.offset;
	    }
	}
	code = eop_jh;
	els[offset] = hswitch;
    }
    v_dec();

    return (code);
}
